from __future__ import with_statement

import threading
import os
import re
from kineta.error import KinetaError


class DeleteHelper(object):

    def __new__(cls, *args, **kwargs):
        o = object.__new__(cls)
        o._pre_init()
        return o


def cache_bytes(size):
    mult = 1

    m = re.search(r'^([0-9\.]+)([gmk]?b)?$', str(size).lower())
    if not m:
        raise KinetaError("Invalid cache size '%s'" % (size, ))
    suf = m.group(2)
    if suf == "gb":
        mult = 1024*1024*1024
    elif suf == "mb":
        mult = 1024*1024
    elif suf == "kb":
        mult = 1024

    r = int(float(m.group(1)) * mult)
    if r < (20*1024):
        raise KinetaError("Cache size too small (%s)" % r)
    return r


def check_pid_file(path):
    fd = open(path, "w+")
    if os.name == 'nt': #pragma: no cover
        import win32file
        import win32con
        import pywintypes
        hfile = win32file._get_osfhandle(fd.fileno())
        try:
            win32file.LockFileEx(hfile, win32con.LOCKFILE_EXCLUSIVE_LOCK|
                                        win32con.LOCKFILE_FAIL_IMMEDIATELY,
                                 0, -0x10000, pywintypes.OVERLAPPED())
        except pywintypes.error:
            fd.close()
            return None

    else:
        import fcntl
        try:
            fcntl.flock(fd.fileno(), fcntl.LOCK_EX|fcntl.LOCK_NB)
        except IOError:
            fd.close()
            return None
    fd.write(str(os.getpid()))
    fd.flush()
    return fd


def get_index_names(db, idx):
    return [[db.dict.get_name(col), dir] for col, dir in idx]


def strkeys(d):
    r = {}
    for k, v in d.iteritems():
        r[str(k)] = v
    return r


def no_strings(colset):
    colsnostr = set()
    for col in colset:
        if not isinstance(col, (str, unicode)):
            colsnostr.add(col)
    return colsnostr


class ReadLock(object):

    def __init__(self, rwlock):
        self._closed = False
        self._lock = rwlock
        rwlock._acquire_read(self)

    def upgrade_to_writer(self):
        self._lock._upgrade_to_write(self)
        return Writer(self, self._lock)

    def close(self):
        if not self._closed:
            self._lock._release_read(self)
            self._closed = True

    def __enter__(self):
        return self

    def __exit__(self, *exc_info):
        self.close()


class Writer(object):

    def __init__(self, rlock, rwlock):
        self._closed = False
        self._lock = rwlock
        self._parent = rlock

    def __enter__(self):
        return self

    def __exit__(self, *exc_info):
        self._lock._downgrade_to_read(self._parent)


class RWLock(object):

    def __init__(self):
        self._condition = threading.Condition()
        self._writer = None
        self._readers = {}

    def _incr_ref(self, l):
        self._readers[l] = self._readers.get(l, 0) + 1

    def _decr_ref(self, l):
        i = self._readers[l]
        if i == 1:
            del self._readers[l]
        else:
            self._readers[l] = i - 1

    def _acquire_read(self, l):
        self._condition.acquire()
        try:
            while True:
                if self._writer is None:
                    self._incr_ref(l)
                    return
                self._condition.wait()
        finally:
            self._condition.release()

    def _release_read(self, l):
        self._condition.acquire()
        try:
            self._decr_ref(l)
            if len(self._readers) == 0:
                self._condition.notifyAll()
        finally:
            self._condition.release()

    def _upgrade_to_write(self, l):
        currthread = threading.currentThread()
        self._condition.acquire()
        try:
            self._decr_ref(l)
            while True:
                if len(self._readers) == 0 and self._writer is None:
                    self._writer = l
                    return
                self._condition.wait()
        finally:
            self._condition.release()

    def _downgrade_to_read(self, l):
        self._condition.acquire()
        try:
            self._writer = None
            self._incr_ref(l)
        finally:
            self._condition.release()
